// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 此文件实现AST的导出筛选。

package doc

import (
	"go/ast"
	"go/token"
)

// filterIdentList从就地列表中删除未报告的名称
// 并返回结果列表。
// None
func filterIdentList(list []*ast.Ident) []*ast.Ident {
	j := 0
	for _, x := range list {
		if token.IsExported(x.Name) {
			list[j] = x
			j++
		}
	}
	return list[0:j]
}

var underscore = ast.NewIdent("_")

func filterCompositeLit(lit *ast.CompositeLit, filter Filter, export bool) {
	n := len(lit.Elts)
	lit.Elts = filterExprList(lit.Elts, filter, export)
	if len(lit.Elts) < n {
		lit.Incomplete = true
	}
}

func filterExprList(list []ast.Expr, filter Filter, export bool) []ast.Expr {
	j := 0
	for _, exp := range list {
		switch x := exp.(type) {
		case *ast.CompositeLit:
			filterCompositeLit(x, filter, export)
		case *ast.KeyValueExpr:
			if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) {
				continue
			}
			if x, ok := x.Value.(*ast.CompositeLit); ok {
				filterCompositeLit(x, filter, export)
			}
		}
		list[j] = exp
		j++
	}
	return list[0:j]
}

// UpdateIdentintList将所有未报告的标识符替换为下划线
// 并报告是否至少存在一个导出的名称。
func updateIdentList(list []*ast.Ident) (hasExported bool) {
	for i, x := range list {
		if token.IsExported(x.Name) {
			hasExported = true
		} else {
			list[i] = underscore
		}
	}
	return hasExported
}

// hasExportedName报告列表是否包含任何导出的名称。
// None
func hasExportedName(list []*ast.Ident) bool {
	for _, x := range list {
		if x.IsExported() {
			return true
		}
	}
	return false
}

// removeErrorField从接口中删除名为“error”的匿名字段。
// 当“error”被确定为本地名称时调用，
// 不是预先声明的类型。
// None
func removeErrorField(ityp *ast.InterfaceType) {
	list := ityp.Methods.List // 我们知道ityp.Methods！=无
	j := 0
	for _, field := range list {
		keepField := true
		if n := len(field.Names); n == 0 {
			// 匿名字段
			if fname, _ := baseTypeName(field.Type); fname == "error" {
				keepField = false
			}
		}
		if keepField {
			list[j] = field
			j++
		}
	}
	if j < len(list) {
		ityp.Incomplete = true
	}
	ityp.Methods.List = list[0:j]
}

// filterFieldList从字段列表中删除未报告的字段（字段名称）
// 并报告字段是否已删除。匿名字段是
// 使用父类型录制。使用的类型调用filterType
// 所有剩余字段。
// None
func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) {
	if fields == nil {
		return
	}
	list := fields.List
	j := 0
	for _, field := range list {
		keepField := false
		if n := len(field.Names); n == 0 {
			// 匿名字段
			fname := r.recordAnonymousField(parent, field.Type)
			if token.IsExported(fname) {
				keepField = true
			} else if ityp != nil && fname == "error" {
				// 可能是预先声明的错误接口；保持
				// 现在，请记住此接口，以便
				// 如果错误也在本地定义，则可以修复该错误
				keepField = true
				r.remember(ityp)
			}
		} else {
			field.Names = filterIdentList(field.Names)
			if len(field.Names) < n {
				removedFields = true
			}
			if len(field.Names) > 0 {
				keepField = true
			}
		}
		if keepField {
			r.filterType(nil, field.Type)
			list[j] = field
			j++
		}
	}
	if j < len(list) {
		removedFields = true
	}
	fields.List = list[0:j]
	return
}

// filterParamList将filterType应用于字段中的每个参数类型。
// None
func (r *reader) filterParamList(fields *ast.FieldList) {
	if fields != nil {
		for _, f := range fields.List {
			r.filterType(nil, f.Type)
		}
	}
}

// filterType从typ中删除任何未报告的结构字段或方法类型
// 在正确的位置如果已删除字段（或方法），则相应的
// 结构或接口类型的“不完整”字段设置为true。
// None
func (r *reader) filterType(parent *namedType, typ ast.Expr) {
	switch t := typ.(type) {
	case *ast.Ident:
		// 无事可做
	case *ast.ParenExpr:
		r.filterType(nil, t.X)
	case *ast.ArrayType:
		r.filterType(nil, t.Elt)
	case *ast.StructType:
		if r.filterFieldList(parent, t.Fields, nil) {
			t.Incomplete = true
		}
	case *ast.FuncType:
		r.filterParamList(t.Params)
		r.filterParamList(t.Results)
	case *ast.InterfaceType:
		if r.filterFieldList(parent, t.Methods, t) {
			t.Incomplete = true
		}
	case *ast.MapType:
		r.filterType(nil, t.Key)
		r.filterType(nil, t.Value)
	case *ast.ChanType:
		r.filterType(nil, t.Value)
	}
}

func (r *reader) filterSpec(spec ast.Spec) bool {
	switch s := spec.(type) {
	case *ast.ImportSpec:
		// 始终保持进口，以便我们可以收集它们
		return true
	case *ast.ValueSpec:
		s.Values = filterExprList(s.Values, token.IsExported, true)
		if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 {
			// 如果在RHS上声明了值，只需替换未报告的值
			// LHS上带有下划线的标识符，以便匹配
			// RHS上的表达序列。
			// None
			// 类似地，如果没有类型和值，则此表达式
			// 必须遵循iota表达式，其中顺序很重要。
			if updateIdentList(s.Names) {
				r.filterType(nil, s.Type)
				return true
			}
		} else {
			s.Names = filterIdentList(s.Names)
			if len(s.Names) > 0 {
				r.filterType(nil, s.Type)
				return true
			}
		}
	case *ast.TypeSpec:
		if name := s.Name.Name; token.IsExported(name) {
			r.filterType(r.lookupType(s.Name.Name), s.Type)
			return true
		} else if name == "error" {
			// 特殊情况：请记住，错误是在本地声明的
			r.errorDecl = true
		}
	}
	return false
}

// copyConstType返回位置为pos的typ副本。
// typ必须是有效的常量类型。
// 实际上，只有（可能是合格的）标识符是可能的。
// None
func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr {
	switch typ := typ.(type) {
	case *ast.Ident:
		return &ast.Ident{Name: typ.Name, NamePos: pos}
	case *ast.SelectorExpr:
		if id, ok := typ.X.(*ast.Ident); ok {
			// 可能是一个限定标识符
			return &ast.SelectorExpr{
				Sel: ast.NewIdent(typ.Sel.Name),
				X:   &ast.Ident{Name: id.Name, NamePos: pos},
			}
		}
	}
	return nil // 不应该发生，但要保守，不要惊慌
}

func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec {
	if tok == token.CONST {
		// 传播否则会丢失的任何类型信息
		// 过滤未报告的常量时。
		var prevType ast.Expr
		for _, spec := range list {
			spec := spec.(*ast.ValueSpec)
			if spec.Type == nil && len(spec.Values) == 0 && prevType != nil {
				// 为当前规范提供显式类型
				spec.Type = copyConstType(prevType, spec.Pos())
			}
			if hasExportedName(spec.Names) {
				// 导出的名称被保留，因此不需要传播类型
				prevType = nil
			} else {
				prevType = spec.Type
			}
		}
	}

	j := 0
	for _, s := range list {
		if r.filterSpec(s) {
			list[j] = s
			j++
		}
	}
	return list[0:j]
}

func (r *reader) filterDecl(decl ast.Decl) bool {
	switch d := decl.(type) {
	case *ast.GenDecl:
		d.Specs = r.filterSpecList(d.Specs, d.Tok)
		return len(d.Specs) > 0
	case *ast.FuncDecl:
		// 可以提前筛选这些方法，因为
		// 冲突的方法也将在此处过滤-
		// 因此，尽早取消这些方法不会导致
		// 错误地消除可能的冲突
		return token.IsExported(d.Name.Name)
	}
	return false
}

// fileExports从src中删除未报告的声明。
// None
func (r *reader) fileExports(src *ast.File) {
	j := 0
	for _, d := range src.Decls {
		if r.filterDecl(d) {
			src.Decls[j] = d
			j++
		}
	}
	src.Decls = src.Decls[0:j]
}
